#!/usr/bin/env python

# vPresent is an immersive presentation creation and display application.
# Copyright (C) 2007 by Infiscape Corporation
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

# This is a simple test program for PyOpenSG clustering.
#
# To test it, run 
#   ./ClusterServer.py -geometry 300x300+200+100 -m -w test1 &
#   ./ClusterServer.py -geometry 300x300+500+100 -m -w test2 &
#   ./ClusterClientAdv.py -m -f Data/tie.wrl test1 test2
#
# If you have trouble with multicasting, you can alternatively try
#   ./ClusterServer.py -geometry 300x300+200+100 -w 127.0.0.1:30000 &
#   ./ClusterServer.py -geometry 300x300+500+100 -w 127.0.0.1:30001 &
#   ./ClusterClientAdv.py -m -f Data/tie.wrl 127.0.0.1:30000 127.0.0.1:30001
# 
# The client will open an emoty window that you can use to navigate. The
# display is shown in the server windows.
#
# This will run all three on the same machine, but you can also start the 
# servers anywhere else, as long as you can reach them via multicast.
#
# Note: This will run two VERY active OpenGL programs on one screen. Not all
# OpenGL drivers are happy with that, so if it crashes your X, it's not our
# fault! ;)

import math
import traceback

import osg2 as osg
from OpenGL.GL import *
from OpenGL.GLUT import *


# Global scene manager reference.
gMgr = None

gClusterWindow = None

# Global client window reference.
gClientWindow = None

gNavigator = osg.Navigator()

gRenderAction = None

gMouseB = 0
gLastX  = 0
gLastY  = 0

# ============================================================================
# GLUT callback functions.
# ============================================================================

def display():
   gNavigator.updateCameraTransformation()

   try:
      osg.Thread.getCurrentChangeList().commitChanges()
      gClusterWindow.activate()
      gClusterWindow.frameInit()
      gClusterWindow.renderAllViewports(gRenderAction)
      gClusterWindow.swap()
      gClusterWindow.frameExit()

      gClientWindow.render(gRenderAction)

      osg.Thread.getCurrentChangeList().clear()
   except:
      traceback.print_exc()
      sys.exit(0)

def idle():
   gNavigator.idle(gMouseB, gLastX, gLastY)

def reshape(w, h):
   glViewport(0, 0, w, h)
   gClientWindow.resize(w, h)
   glutPostRedisplay()

def mouse(button, state, x, y):
   global gMouseB

   btn = None
   if button == GLUT_LEFT_BUTTON:
      btn = osg.Navigator.LEFT_MOUSE
   elif button == GLUT_MIDDLE_BUTTON:
      btn = osg.Navigator.MIDDLE_MOUSE
   elif button == GLUT_RIGHT_BUTTON:
      btn = osg.Navigator.RIGHT_MOUSE
   elif button == GLUT_UP:
      btn = osg.Navigator.UP_MOUSE
   elif button == GLUT_DOWN:
      btn = osg.Navigator.DOWN_MOUSE
   else:
      print "WARNING: Unknown button", button

   if state:
      if btn is not None:
         gNavigator.buttonRelease(btn, x, y)
      gMouseB =  gMouseB & ~(1 << button)
   else:
      if btn is not None:
         gNavigator.buttonPress(btn, x, y)
      gMouseB = gMouseB | 1 << button

   global gLastX
   global gLastY

   gLastX = x
   gLastY = y

   glutPostRedisplay()

def motion(x, y):
   if gMouseB:
      gNavigator.moveTo(x, y)

   global gLastX
   global gLastY
   gLastX = x
   gLastY = y

   glutPostRedisplay()

def keyboard(k, x, y):
   if ord(k) == 27:
      osg.osgExit()
      sys.exit(0)
   elif k == 'j':
      gNavigator.keyPress(osg.Navigator.LEFT, x, y)
   elif k == 'g':
      gNavigator.keyPress(osg.Navigator.RIGHT, x, y)
   elif k == 'u':
      gNavigator.keyPress(osg.Navigator.LEFTROT, x, y)
   elif k == 't':
      gNavigator.keyPress(osg.Navigator.RIGHTROT, x, y)
   elif k == 'y':
      gNavigator.keyPress(osg.Navigator.FORWARDS, x, y)
   elif k == 'h':
      gNavigator.keyPress(osg.Navigator.BACKWARDS, x, y)

def init(filenames, serverX, serverY, serviceAddress):
   glEnable(GL_DEPTH_TEST)
   glEnable(GL_LIGHTING)
   glEnable(GL_LIGHT0)
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1)

   up_vec = osg.Vec3f(0.0, 1.0, 0.0)

   camera_beacon = osg.Node.create()
   camera_beacon_core = osg.Transform.create()
   camera_beacon.setCore(camera_beacon_core)
   camera_from = osg.Pnt3f(0.0, 0.0, 0.0)
   #camera_at   = osg.Pnt3f(0.0, 2.5, -10.0)
   camera_at   = osg.Pnt3f(0.0, 0.0, -1.0)
   look_at_mat = osg.Matrix()
   osg.MatrixLookAt(look_at_mat, camera_from, camera_at, up_vec)
   camera_beacon_core.setMatrix(look_at_mat)

   light_beacon = osg.Node.create()
   light_beacon_core = osg.Group.create()
   light_beacon.setCore(light_beacon_core)

   light_xform = osg.Node.create()
   light_xform_core = osg.Transform.create()
   light_xform.setCore(light_xform_core)
   light_xform.addChild(light_beacon)

   dlight = osg.Node.create()
   dl = osg.DirectionalLight.create()
   dlight.setCore(dl)

   dl.setAmbient(.0, .0, .0, 1)
   dl.setDiffuse(.8, .8, .8, .8)
   dl.setDirection(0, 0, 1)
   dl.setBeacon(light_beacon)

   real_root = osg.Node.create()
   real_root.setCore(osg.Group.create())

   root = osg.Node.create()
   root.setCore(osg.Group.create())
   root.addChild(light_xform)
   root.addChild(dlight)

   real_root.addChild(root)

   scene = osg.Node.create()
   scene.setCore(osg.Group.create())

   if len(filenames) == 0:
      scene.addChild(osg.makeTorus(0.5, 2, 16, 16))
   else:
      for f in filenames:
         file = osg.SceneFileHandler.the().read(f)
         if file:
            scene.addChild(file)

#   prepareSceneGraph(scene)

   osg.commitChanges()

   scene.invalidateVolume()
   scene.updateVolume()

   dlight.addChild(scene)
   dlight.updateVolume()

   camera = osg.PerspectiveCamera.create()
   camera.setBeacon(camera_beacon)
   camera.setFov(math.radians(60))
   camera.setNear(0.1)
   camera.setFar(10000.0)

   from_point = osg.Pnt3f(0.0, 0.0, 0.0)
   servers = gClusterWindow.getServers()

   bg = osg.SolidBackground.create()
   bg.setColor(osg.Color3f(0, 0, 0))

   at_pts = [osg.Pnt3f(-1.0, 0.0, 0.0), osg.Pnt3f(0.0, 0.0, -1.0),
             osg.Pnt3f(1.0, 0.0, 0.0)]
   for i in range(len(servers)):
      vp = osg.Viewport.create()

      camera_decorator = osg.MatrixCameraDecorator.create()
      camera_decorator.setDecoratee(camera)
      mat = osg.Matrix()
      osg.MatrixLookAt(mat, from_point, at_pts[i], up_vec)
      camera_decorator.setPostViewing(mat)
      vp.setCamera(camera_decorator)
      vp.setBackground(bg)
      vp.setRoot(root)
      vp.setSize(0, 0, 1, 1)
      gClusterWindow.addPort(vp)

   if serverX > 0 and serverY > 0:
      gClusterWindow.setSize(serverX, serverY)

   # Create a viewport for gClientWindow that displays the entire scene.
   vp = osg.Viewport.create()
   vp.setCamera(camera)
   vp.setBackground(bg)
   vp.setRoot(real_root)
   vp.setSize(0, 0, 1, 1)
   gClientWindow.addPort(vp)

   if serviceAddress:
      gClusterWindow.setServiceAdress(servivceAddress)

   volume = scene.getVolume()
   min = osg.Pnt3f()
   max = osg.Pnt3f()
   volume.getBounds(min, max)
   size = max - min

#   pos = osg.Vec3f(min[0] + ((max[0] - min[0]) * 0.5),
#                   min[1] + ((max[1] - min[1]) * 0.5),
#                   max[2] + (max[2] - min[2]) * 1.5)
#   scale = (max[2] - min[2] + max[1] - min[1] + max[0] - min[0]) / 6

   gNavigator.setMode(osg.Navigator.TRACKBALL)
   gNavigator.setViewport(gClusterWindow.getPort(0))
   gNavigator.setCameraTransformation(camera_beacon)
   gNavigator.set(camera_from, camera_at, up_vec)
   gNavigator.setMotionFactor((size[0] + size[1] + size[2]) / 100.0)

   camera.setFar(size.length() * 100.0)
   camera.setNear(size.length() * 100.0 / 10000.0)

# ============================================================================
# Main function
# ============================================================================

def main():
   scene = None

   args = sys.argv[1:]

   client_rendering = True
   win_width        = 300
   win_height       = 300
   files            = []
   servers          = []

   server_x = -1
   server_y = -1

   connection_type        = 'Multicast'
   connection_destination = ''
   connection_interface   = ''
   connection_parameters  = ''
   service_address        = None

   while len(args) > 0:
      arg = args.pop(0)

      if arg.startswith('-'):
         if arg[1] == 'm':
            connection_type = 'Multicast'
            print "Connection type set to Multicast"
         elif arg[1] == 'p':
            connection_type = 'SockPipeline'
            print "Connection type set to SockPipeline"
         elif arg[1] == 'o':
            try:
               if len(arg) > 2:
                  opt = arg[2:]
               else:
                  opt = args.pop(0)
            except:
               opt = None

            if opt:
               connection_parameters = opt
         elif arg[1] == 'i':
            try:
               if len(arg) > 2:
                  opt = arg[2:]
               else:
                  opt = args.pop(0)
            except:
               opt = None

            if opt:
               connection_interface = opt
         elif arg[1] == 'b':
            try:
               if len(arg) > 2:
                  opt = arg[2:]
               else:
                  opt = args.pop(0)
            except:
               opt = None

            if opt:
               service_address = opt
         elif arg[1] == 'G':
            try:
               if len(arg) > 2:
                  opt = arg[2:]
               else:
                  opt = args.pop(0)
            except:
               opt = None

            if opt:
               connection_destination = opt
         elif arg[1] == 'd':
            client_rendering = False
         elif arg[1] == 'f':
            try:
               if len(arg) > 2:
                  opt = arg[2:]
               else:
                  opt = args.pop(0)
            except:
               opt = None

            if opt:
               files.append(opt)
         elif arg[1] == 'g':
            if len(arg) > 2:
               opt = arg[2:]
            else:
               opt = args.pop(0)

            if opt:
               geom = opt.split(',')
               win_width  = int(geom[0])
               win_height = int(geom[1])
         elif arg[1] == 'x':
            try:
               if len(arg) > 2:
                  opt = arg[2:]
               else:
                  opt = args.pop(0)
            except:
               opt = None

            if opt:
               server_x = int(opt)
         elif arg[1] == 'y':
            try:
               if len(arg) > 2:
                  opt = arg[2:]
               else:
                  opt = args.pop(0)
            except:
               opt = None

            if opt:
               server_y = int(opt)
      else:
         print arg
         servers.append(arg)

   # Initialize OpenSG and GLUT.
   osg.osgInit(sys.argv)
   glutInit(sys.argv)
   glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE)

   glutInitWindowSize(win_width, win_height)
   winid = glutCreateWindow("PyOpenSG Cluster Client")

   glutReshapeFunc(reshape)
   glutDisplayFunc(display)
   glutIdleFunc(idle)
   glutMouseFunc(mouse)
   glutMotionFunc(motion)
   glutKeyboardFunc(keyboard)

   global gRenderAction
   gRenderAction = osg.RenderAction.create()
   #gRenderAction = osg.RenderTraversalAction.create()

   # Clear change list from prototypes.
   osg.Thread.getCurrentChangeList().clear()

   global gClusterWindow
   gClusterWindow = osg.MultiDisplayWindow.create()

   for s in servers:
      gClusterWindow.pushToServers(s)

   global gClientWindow
   glutReshapeWindow(win_width, win_height)
   gClientWindow = osg.GLUTWindow.create()
   gClientWindow.setId(winid)
   gClientWindow.init()

   init(files, server_x, server_y, service_address)

   gClusterWindow.setConnectionType(connection_type)
   gClusterWindow.setConnectionParams(connection_parameters)

   gClusterWindow.init()
   gClusterWindow.resize(win_width, win_height)
   gClientWindow.resize(win_width, win_height)
   gClusterWindow.setConnectionDestination(connection_destination)
   gClusterWindow.setConnectionInterface(connection_interface)

   glutMainLoop()

   sys.exit(0)

if __name__ == '__main__':
   main()
